// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008-2014 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef EIGEN_COMPRESSED_STORAGE_H
#define EIGEN_COMPRESSED_STORAGE_H

namespace Eigen {

namespace internal {

    /** \internal
  * Stores a sparse set of values as a list of values and a list of indices.
  *
  */
    template <typename _Scalar, typename _StorageIndex> class CompressedStorage
    {
    public:
        typedef _Scalar Scalar;
        typedef _StorageIndex StorageIndex;

    protected:
        typedef typename NumTraits<Scalar>::Real RealScalar;

    public:
        CompressedStorage() : m_values(0), m_indices(0), m_size(0), m_allocatedSize(0) {}

        explicit CompressedStorage(Index size) : m_values(0), m_indices(0), m_size(0), m_allocatedSize(0) { resize(size); }

        CompressedStorage(const CompressedStorage& other) : m_values(0), m_indices(0), m_size(0), m_allocatedSize(0) { *this = other; }

        CompressedStorage& operator=(const CompressedStorage& other)
        {
            resize(other.size());
            if (other.size() > 0)
            {
                internal::smart_copy(other.m_values, other.m_values + m_size, m_values);
                internal::smart_copy(other.m_indices, other.m_indices + m_size, m_indices);
            }
            return *this;
        }

        void swap(CompressedStorage& other)
        {
            std::swap(m_values, other.m_values);
            std::swap(m_indices, other.m_indices);
            std::swap(m_size, other.m_size);
            std::swap(m_allocatedSize, other.m_allocatedSize);
        }

        ~CompressedStorage()
        {
            delete[] m_values;
            delete[] m_indices;
        }

        void reserve(Index size)
        {
            Index newAllocatedSize = m_size + size;
            if (newAllocatedSize > m_allocatedSize)
                reallocate(newAllocatedSize);
        }

        void squeeze()
        {
            if (m_allocatedSize > m_size)
                reallocate(m_size);
        }

        void resize(Index size, double reserveSizeFactor = 0)
        {
            if (m_allocatedSize < size)
            {
                Index realloc_size = (std::min<Index>)(NumTraits<StorageIndex>::highest(), size + Index(reserveSizeFactor * double(size)));
                if (realloc_size < size)
                    internal::throw_std_bad_alloc();
                reallocate(realloc_size);
            }
            m_size = size;
        }

        void append(const Scalar& v, Index i)
        {
            Index id = m_size;
            resize(m_size + 1, 1);
            m_values[id] = v;
            m_indices[id] = internal::convert_index<StorageIndex>(i);
        }

        inline Index size() const { return m_size; }
        inline Index allocatedSize() const { return m_allocatedSize; }
        inline void clear() { m_size = 0; }

        const Scalar* valuePtr() const { return m_values; }
        Scalar* valuePtr() { return m_values; }
        const StorageIndex* indexPtr() const { return m_indices; }
        StorageIndex* indexPtr() { return m_indices; }

        inline Scalar& value(Index i)
        {
            eigen_internal_assert(m_values != 0);
            return m_values[i];
        }
        inline const Scalar& value(Index i) const
        {
            eigen_internal_assert(m_values != 0);
            return m_values[i];
        }

        inline StorageIndex& index(Index i)
        {
            eigen_internal_assert(m_indices != 0);
            return m_indices[i];
        }
        inline const StorageIndex& index(Index i) const
        {
            eigen_internal_assert(m_indices != 0);
            return m_indices[i];
        }

        /** \returns the largest \c k such that for all \c j in [0,k) index[\c j]\<\a key */
        inline Index searchLowerIndex(Index key) const { return searchLowerIndex(0, m_size, key); }

        /** \returns the largest \c k in [start,end) such that for all \c j in [start,k) index[\c j]\<\a key */
        inline Index searchLowerIndex(Index start, Index end, Index key) const
        {
            while (end > start)
            {
                Index mid = (end + start) >> 1;
                if (m_indices[mid] < key)
                    start = mid + 1;
                else
                    end = mid;
            }
            return start;
        }

        /** \returns the stored value at index \a key
      * If the value does not exist, then the value \a defaultValue is returned without any insertion. */
        inline Scalar at(Index key, const Scalar& defaultValue = Scalar(0)) const
        {
            if (m_size == 0)
                return defaultValue;
            else if (key == m_indices[m_size - 1])
                return m_values[m_size - 1];
            // ^^  optimization: let's first check if it is the last coefficient
            // (very common in high level algorithms)
            const Index id = searchLowerIndex(0, m_size - 1, key);
            return ((id < m_size) && (m_indices[id] == key)) ? m_values[id] : defaultValue;
        }

        /** Like at(), but the search is performed in the range [start,end) */
        inline Scalar atInRange(Index start, Index end, Index key, const Scalar& defaultValue = Scalar(0)) const
        {
            if (start >= end)
                return defaultValue;
            else if (end > start && key == m_indices[end - 1])
                return m_values[end - 1];
            // ^^  optimization: let's first check if it is the last coefficient
            // (very common in high level algorithms)
            const Index id = searchLowerIndex(start, end - 1, key);
            return ((id < end) && (m_indices[id] == key)) ? m_values[id] : defaultValue;
        }

        /** \returns a reference to the value at index \a key
      * If the value does not exist, then the value \a defaultValue is inserted
      * such that the keys are sorted. */
        inline Scalar& atWithInsertion(Index key, const Scalar& defaultValue = Scalar(0))
        {
            Index id = searchLowerIndex(0, m_size, key);
            if (id >= m_size || m_indices[id] != key)
            {
                if (m_allocatedSize < m_size + 1)
                {
                    m_allocatedSize = 2 * (m_size + 1);
                    internal::scoped_array<Scalar> newValues(m_allocatedSize);
                    internal::scoped_array<StorageIndex> newIndices(m_allocatedSize);

                    // copy first chunk
                    internal::smart_copy(m_values, m_values + id, newValues.ptr());
                    internal::smart_copy(m_indices, m_indices + id, newIndices.ptr());

                    // copy the rest
                    if (m_size > id)
                    {
                        internal::smart_copy(m_values + id, m_values + m_size, newValues.ptr() + id + 1);
                        internal::smart_copy(m_indices + id, m_indices + m_size, newIndices.ptr() + id + 1);
                    }
                    std::swap(m_values, newValues.ptr());
                    std::swap(m_indices, newIndices.ptr());
                }
                else if (m_size > id)
                {
                    internal::smart_memmove(m_values + id, m_values + m_size, m_values + id + 1);
                    internal::smart_memmove(m_indices + id, m_indices + m_size, m_indices + id + 1);
                }
                m_size++;
                m_indices[id] = internal::convert_index<StorageIndex>(key);
                m_values[id] = defaultValue;
            }
            return m_values[id];
        }

        void moveChunk(Index from, Index to, Index chunkSize)
        {
            eigen_internal_assert(to + chunkSize <= m_size);
            if (to > from && from + chunkSize > to)
            {
                // move backward
                internal::smart_memmove(m_values + from, m_values + from + chunkSize, m_values + to);
                internal::smart_memmove(m_indices + from, m_indices + from + chunkSize, m_indices + to);
            }
            else
            {
                internal::smart_copy(m_values + from, m_values + from + chunkSize, m_values + to);
                internal::smart_copy(m_indices + from, m_indices + from + chunkSize, m_indices + to);
            }
        }

        void prune(const Scalar& reference, const RealScalar& epsilon = NumTraits<RealScalar>::dummy_precision())
        {
            Index k = 0;
            Index n = size();
            for (Index i = 0; i < n; ++i)
            {
                if (!internal::isMuchSmallerThan(value(i), reference, epsilon))
                {
                    value(k) = value(i);
                    index(k) = index(i);
                    ++k;
                }
            }
            resize(k, 0);
        }

    protected:
        inline void reallocate(Index size)
        {
#ifdef EIGEN_SPARSE_COMPRESSED_STORAGE_REALLOCATE_PLUGIN
            EIGEN_SPARSE_COMPRESSED_STORAGE_REALLOCATE_PLUGIN
#endif
            eigen_internal_assert(size != m_allocatedSize);
            internal::scoped_array<Scalar> newValues(size);
            internal::scoped_array<StorageIndex> newIndices(size);
            Index copySize = (std::min)(size, m_size);
            if (copySize > 0)
            {
                internal::smart_copy(m_values, m_values + copySize, newValues.ptr());
                internal::smart_copy(m_indices, m_indices + copySize, newIndices.ptr());
            }
            std::swap(m_values, newValues.ptr());
            std::swap(m_indices, newIndices.ptr());
            m_allocatedSize = size;
        }

    protected:
        Scalar* m_values;
        StorageIndex* m_indices;
        Index m_size;
        Index m_allocatedSize;
    };

}  // end namespace internal

}  // end namespace Eigen

#endif  // EIGEN_COMPRESSED_STORAGE_H
